<?php
/**
 * Created by PhpStorm.
 * Author: sitewang@aliyun.com
 * CreateTime: 2022/11/05 2022/11/5
 * Blog：www.myblogs.xyz
 */

namespace app\common\service;

class WxPayService
{
    protected $appId = ''; // 微信应用AppId
    protected $mchId = ''; // 商户号
    protected $key = ''; // 商户API秘钥
    protected $mchPrivateKey = ''; // 商户私钥
    protected $serialNo = '4D31FFB20AD397257A45FABA211F29D62122BD71'; // 证书序列号
    protected $error = ''; // 错误信息
    protected $httpUrl = 'https://api.mch.weixin.qq.com';

    public function __construct($type = 'wx_pay')
    {
        $payment_data = config('payment.wx_app');
        $this->appId = $payment_data['app_id'];
        $this->mchId = $payment_data['mch_id'];
        $this->key = $payment_data['key'];
        $private_key = app_path() . $payment_data['cert_key'];
        $this->mchPrivateKey = $this->getPrivateKey($private_key);
    }

    public function cert()
    {
        $url = '/v3/certificates';
        $nonce_str = $this->getNonceStr();
        $sign = $this->sign($url, 'GET', NEW_TIME, $nonce_str);
        $header[] = 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36';
        $header[] = 'Accept: application/json';
        $header[] = 'Authorization: WECHATPAY2-SHA256-RSA2048 '.$sign;
        $dara = $this->curlGet($url, $header);

        return $dara;
    }

    // APP支付（APP）
    public function wxAppPay($param = [])
    {
        // wx_apy
        $result = $this->into('app', $param);
        if (empty($result->prepay_id)){
           return false;
        }
        $curr_time = time();
        $nonce_str = $this->getNonceStr();
        $pay_param = [
            'appid'=>$this->appId,
            'partnerid'=>$this->mchId,
            'prepayid'=>$result->prepay_id,
            'package'=>'Sign=WXPay',
            'noncestr'=>$nonce_str,
            'timestamp'=>$curr_time,
        ];
        $sign = $this->makeSign($curr_time, $nonce_str, $result->prepay_id);
        $pay_param['sign'] = $sign;
        return $pay_param;
    }

    // 微信公众号/小程序支付（JS API）
    public function wxJsApiPay($param)
    {
        // wx_public
        return $this->into('jsapi', $param);
    }

    // H5、APP/公众号（H5）
    public function wxH5Pay($param)
    {
        // wx_public
        return $this->into('h5', $param);
    }

    // 扫码支付、APP/公众号/小程序（Native）
    public function wxNativePay($param)
    {
        // wx_applet
        return $this->into('native', $param);
    }

    /**
     * 统一下单
     * @param $type
     * @param array $param
     * @return bool|mixed
     */
    private function into($type, $param = [])
    {
        if (empty($param)) return false;
        $url = '/v3/pay/transactions/' . $type;
        // 支付失效时间（5分钟）
        $curr_time = NEW_TIME;
        $date = date('Y-m-d', $param['time_expire']);
        $time = date('H:i:s', $param['time_expire']);
        $time_expire = $date . 'T' . $time . '+08:00';
        // 发送数据
        $post_data = [
            'appid'=>$this->appId,
            'mchid'=>$this->mchId,
            'description'=>$param['body'],
            'out_trade_no'=>$param['order_code'],
            'time_expire'=>$time_expire,
            'notify_url'=>$param['notify_url'],
            'amount'=>[
                'total'=>floatval($param['money'])*100,
                'currency'=>'CNY'
            ]
        ];
        if ($type == 'jsapi'){
            if (empty($param['openid'])){
                $this->error = '缺少openid';
                return false;
            }
            $post_data['payer'] = [
                'openid'=>$param['openid']
            ];
        }
        if ($type == 'h5'){
            $post_data['scene_info'] = [
                'payer_client_ip'=>get_client_ip(),
                'h5_info'=>[
                    'type'=>'Wap'
                ]
            ];
        }
        $parameters = json_encode($post_data);
        // 随机字符串
        $nonce_str = $this->getNonceStr();
        // post请求$http_method要大写
        $sign = $this->sign($url, 'POST', $curr_time, $nonce_str, $parameters);

        $header[] = 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36';
        $header[] = 'Accept: application/json';
        $header[] = 'Content-Type: application/json';
        $header[] = 'Authorization: WECHATPAY2-SHA256-RSA2048 '.$sign;
        $result = $this->curlPost($url, $parameters, $header);

        return $result;
    }

    // 订单号查询
    public function orderQuery($trade_code)
    {
        $url = '/v3/pay/transactions/id/' . $trade_code;
        $result = $this->curlGet($url);
        return $result;
    }

    /**
     * @param $url
     * @param array $header
     * @param int $timeout
     * @return mixed
     */
    public function curlGet($url, $header = [],  $timeout = 30)
    {
        $ch = curl_init();
        // 设置抓取的url
        curl_setopt($ch, CURLOPT_URL, $this->httpUrl . $url);
        // 设置头文件的信息作为数据流输出
        curl_setopt($ch, CURLOPT_HEADER, 0);
        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
        // 设置获取的信息以文件流的形式返回，而不是直接输出。
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        // 执行命令
        $response = curl_exec($ch);
        if ($error = curl_error($ch)) {
            die($error);
        }
        curl_close($ch);
        return $response;
    }

    /**
     * @param $url
     * @param $data
     * @param array $header
     * @param string $referer
     * @param int $timeout
     * @return mixed
     */
    public function curlPost($url, $data , $header = [], $referer = '', $timeout = 30)
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $this->httpUrl . $url);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
        // 避免https的ssl验证
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_SSLVERSION, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_POST, false);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
        // 模拟来源
        curl_setopt($ch, CURLOPT_REFERER, $referer);
        $response = curl_exec($ch);
        if ($error = curl_error($ch)) {
            die($error);
        }
        curl_close($ch);
        return json_decode($response);
    }

    /**
     * @param $url [请求目标]
     * @param $http_method [请求方式 GET POST PUT]
     * @param $timestamp [时间戳]
     * @param $nonce_str [随机串]
     * @param $body [报文 GET请求时可以为空]
     * @return string
     */
    private function sign($url, $http_method, $timestamp, $nonce_str, $body = '')
    {
        if (!in_array('sha256WithRSAEncryption', \openssl_get_md_methods(true))) {
            throw new BadRequestException("当前PHP环境不支持SHA256withRSA");
        }
        $url_parts = parse_url($url);
        $canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
        $message = $http_method ."\n". $canonical_url ."\n". $timestamp ."\n". $nonce_str ."\n". $body ."\n";

        openssl_sign($message, $raw_sign, $this->mchPrivateKey, 'sha256WithRSAEncryption');
        $sign = base64_encode($raw_sign);

        $token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
            $this->mchId, $nonce_str, $timestamp, $this->serialNo, $sign);

        return $token;
    }

    /**
     * @param $timestamp
     * @param $nonce_str
     * @param $prepay_id
     * @return string
     */
    private function makeSign($timestamp, $nonce_str, $prepay_id = '')
    {
        if (!in_array('sha256WithRSAEncryption', \openssl_get_md_methods(true))) {
            throw new BadRequestException("当前PHP环境不支持SHA256withRSA");
        }
        $message = $this->appId ."\n". $timestamp ."\n". $nonce_str ."\n". $prepay_id ."\n";

        openssl_sign($message, $raw_sign, $this->mchPrivateKey, 'sha256WithRSAEncryption');
        $sign = base64_encode($raw_sign);

        return $sign;
    }

    /**
     * 获取私钥
     * @param $file_path
     * @return bool|resource
     */
    private function getPrivateKey($file_path)
    {
        return openssl_get_privatekey(file_get_contents($file_path));
    }

    /**
     * 产生随机字符串，不长于32位
     * @param int $length
     * @return string
     */
    private static function getNonceStr($length = 32)
    {
        $chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
        $str = '';
        for ( $i = 0; $i < $length; $i++ )  {
            $str .= substr($chars, mt_rand(0, strlen($chars)-1), 1);
        }
        return $str;
    }
}